package com.transactional.service;

import com.transactional.bean.User;
import com.transactional.dao.UserDao;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.io.IOException;

/**
 * 参考：https://www.cnblogs.com/mcahkf/p/9228609.html
 * https://jingyan.baidu.com/article/1e5468f960a64e484861b74b.html
 * https://blog.csdn.net/paincupid/article/details/48185597
 * https://my.oschina.net/zjllovecode/blog/1863103
 */


@Service
public class UserService {
    @Autowired
    private UserDao userDao;
    @Autowired
    private UserServiceB userServiceB;
    @Autowired
    private UserServiceC userServiceC;
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;

    /**
     * 异常导致的事物问题：设置rollbackFor属性，很多博客说默认只有RuntimeExcetion会触发回滚，
     * 经验证确实如此，所以rollbackFor最好应该设置如下：rollbackFor = {Exception.class}，
     * 当然具体业务具体处理，可能有的业务抛出的某些异常并不需要触发回滚，所以此时应该细化处理异常。
     * @param user
     * @return
     */
    @Transactional
    public int insert(User user){
        int i = userDao.insert(user);
        return i;
    }

    /**
     * @Transactional只有用在public方法上才会起作用 ：事务只有配置在public方法上
     * @param user
     * @return
     */
    @Transactional
    public int insert1(User user){
        int i = userDao.insert(user);
        return i;
    }



    /**
     * 事物嵌套：https://www.cnblogs.com/mcahkf/p/9228609.html
     * https://jingyan.baidu.com/article/1e5468f960a64e484861b74b.html
     * https://blog.csdn.net/paincupid/article/details/48185597
     * https://my.oschina.net/zjllovecode/blog/1863103
     *
     *
     * 一般我们不关心外层调用方法的事务传播行为（用默认的（不指定就行））。而只关心内层被调用方法的传播行为。

     我们一般情况下，会有以下三种需求：

     外层调用方法和内层被调用方法，有异常一起回滚，没问题一起提交。（共用一个事务）
     内层被调用方法回滚与否，不会影响外层调用方法。而外层调用方法出异常回滚，也不会回滚内层被调用方法（两个独立的事务）
     内层被调用方法回滚与否，不会影响外层调用方法。而外层调用方法出异常回滚，也会回滚内层被调用方法（嵌套事务）

     这三种情况正好对应三种最常用的传播行为



     1----->@Transactional(propagation=Propagation.REQUIRED) ：

     内外层方法共用外层方法的事务

     2----->@Transactional(propagation=Propagation.REQUIRES_NEW) ：

     当执行内层被调用方法时，外层方法的事务会挂起。两个事务相互独立，不会相互影响。

     3----->@Transactional(propagation=Propagation.NESTED) :

     理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是，PROPAGATION_REQUIRES_NEW另起一个事务，将会与他的父事务相互独立，
     而Nested的事务和他的父事务是相依的，他的提交是要等和他的父事务一块提交的。也就是说，如果父事务最后回滚，他也要回滚的。
     Propagation.NESTED 和 Propagation.REQUIRED的区别：NESTED内层事物的回滚不导致外层事物的回滚，但是外层事物的回滚会导致内层事物的回滚。
     REQUIRED内层事物的回滚导致外层事物的
     回滚

     * @param user
     * @return
     */
    @Transactional
    public int insert2(User user){
        userDao.insert(user);
        int i =  this.insert(user);
        /*try {
            int a = 1/0;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }*/
        return i;
    }
    /**
     * 注意事项三：去掉this.insert(user);的 @Transactional，以下方法的事物也会生效
     * @param user
     * @return
     */
    @Transactional
    public int insert2_1(User user){
        int i =  this.insert(user);
        try {
            int a = 1/0;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return 0;
    }

    /**
     * 内部类调用生效的问题:下面方法insert2_2的事物不起作用，因为insert2_3没有用@Transactional修饰.原因：http://www.sohu.com/a/297514757_100109711
     * （要想起作用：使用AopContext.currentProxy()来获取代理类再调用），但是在外部类调用insert2_2时起作用。)
     * 要想起作用 在类上加@Transactional
     * @param user
     * @return
     */

    public int insert2_3(User user){
//        ((UserService)AopContext.currentProxy()).insert2_2(user);
        int i = insert2_2(user);
        return i;
    }

    @Transactional
    public int insert2_2(User user){
        int i = userDao.insert(user);
//        int a = 1/0;
        return i;
    }



    /**
     * 事物嵌套:userServiceC.insert抛出异常，userDao.insert(user)和userServiceB.insert(user)会回滚。
     * 这里的所有事物传播属性都是PROPAGATION_REQUIRED
     * @param user
     * @return
     */
    @Transactional
    public int insert3(User user){
        int i = 0;
        int j = 0;
        int x = 0;
        try {
            x = userDao.insert(user);
            i = userServiceB.insert(user);
            j = userServiceC.insert(user);
        } catch (Exception e) {
            throw new RuntimeException();
        }
        return i+j;
    }

    /**
     * 事物嵌套:userServiceC.insertNew事物属性为REQUIRES_NEW，userServiceB.insertNew事物抛出异常后，userServiceC.insertNew事物不会回滚
     * @param user
     * @return
     */
    @Transactional
    public int insert4(User user){
        int i = 0;
        int j = 0;
        int x = 0;
        try {
            x = userDao.insert(user);
            j = userServiceC.insertNew(user);
            i = userServiceB.insertNew(user);
        } catch (Exception e) {
            throw new RuntimeException();
        }
        return i+j;
    }


    /**
     * 手动控制事物
     * @param user
     */
    public void manualController(User user) throws Exception {
        // 事务定义
        DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();

        //置事务的传播行为
        transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);

        userDao.insert(user);

        try {
            int i = 1/0;
        }catch (Exception e){

            dataSourceTransactionManager.rollback(transactionStatus);

            throw new RuntimeException();

        }

        dataSourceTransactionManager.commit(transactionStatus);


    }


}
